﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing;
using Perceiveit.Xml.Converters;

namespace Perceiveit.Xml
{
    /// <summary>
    /// The XConverter class provides a base implementation for
    /// conversion of strings to specific types and back again.
    /// You cannot create instances of this type, specific converters
    /// are provided in the Converters namespace for the common types - use the 
    /// static CreateConverter() method to access instances of these types.
    /// </summary>
    /// <typeparam name="T">The type this converter can parse and convert back to strings</typeparam>
    public abstract class XConverter<T> : IXConverter
    {
        //
        // public methods
        //

        #region public Type ConvertsType {get;}

        /// <summary>
        /// Gets the type this converter converts to and from string values
        /// </summary>
        public Type ConvertsType
        {
            get { return typeof(T); }
        }

        #endregion

        #region public abstract string ToString(T value);

        /// <summary>
        /// Converts the value to a string representation
        /// </summary>
        /// <param name="value">The value to convert</param>
        /// <returns>The string representation</returns>
        public abstract string ToString(T value);

        #endregion

        #region public abstract T FromString(string value);

        /// <summary>
        /// Converts the string representation to a value of type T
        /// </summary>
        /// <param name="value">The string value to parse (could be null or empty)</param>
        /// <returns>The parsed value of T from its string representation</returns>
        public abstract T FromString(string value);

        #endregion

        //
        // Interface implementations
        //

        #region IXConverter Members

        object IXConverter.ObjectFromString(string value)
        {
            return FromString(value);
        }

        string IXConverter.ObjectToString(object value)
        {
            return ToString((T)value);
        }

        #endregion

        //
        // static factory methods
        //

        #region private static bool IsNullableType(Type theType)

        /// <summary>
        /// Checks the theType parameter to see if it is a Nullable type (e.g. bool?).
        /// </summary>
        /// <param name="theType">The type type</param>
        /// <returns>True if theType is nullable otherwise false</returns>
        private static bool IsNullableType(Type theType)
        {
            return (theType.IsGenericType && theType.GetGenericTypeDefinition().Equals(typeof(Nullable<>)));
        }

        #endregion

        #region public static XConverter<T> CreateConverter()

        /// <summary>
        /// Creates a standard XConverter&lt;T&gt; instance for the standard type T
        /// </summary>
        /// <returns>A new converter</returns>
        /// <exception cref="ArgumentException" >Thrown if the type T is not a known standard type</exception>
        public static XConverter<T> CreateConverter()
        {
            IXConverter conv = null;
            Type t = typeof(T);
            try
            {
                bool nullable = false;
                if (IsNullableType(t))
                {
                    t = Nullable.GetUnderlyingType(t);
                    nullable = true;
                }
                if (t.IsEnum)
                {
                    if (nullable)
                        throw new ArgumentException("Cannot use a converter on a  nullable enumeration with generic arguments");
                    else
                        conv = new EnumXConverter<T>();

                }
                else
                {
                    TypeCode tc = System.Type.GetTypeCode(t);

                    switch (tc)
                    {
                        case TypeCode.Boolean:
                            if (nullable)
                                conv = new NullableXConverter<bool>(new BoolXConverter());
                            else
                                conv = new BoolXConverter();
                            break;

                        case TypeCode.Byte:
                            if (nullable)
                                conv = new NullableXConverter<byte>(new ByteXConverter());
                            else
                                conv = new ByteXConverter();
                            break;

                        case TypeCode.Char:
                            if (nullable)
                                conv = new NullableXConverter<char>(new CharXConverter());
                            else
                                conv = new CharXConverter();
                            break;

                        case TypeCode.DateTime:
                            if (nullable)
                                conv = new NullableXConverter<DateTime>(new DateTimeXConverter());
                            else
                                conv = new DateTimeXConverter();
                            break;

                        case TypeCode.Decimal:
                            if (nullable)
                                conv = new NullableXConverter<Decimal>(new DecimalXConverter());
                            else
                                conv = new DecimalXConverter();
                            break;

                        case TypeCode.Double:
                            if (nullable)
                                conv = new NullableXConverter<Double>(new DoubleXConverter());
                            else
                                conv = new DoubleXConverter();
                            break;

                        case TypeCode.Int16:
                            if (nullable)
                                conv = new NullableXConverter<short>(new ShortXConverter());
                            else
                                conv = new ShortXConverter();
                            break;

                        case TypeCode.Int32:
                            if (nullable)
                                conv = new NullableXConverter<int>(new IntXConverter());
                            else
                                conv = new IntXConverter();
                            break;

                        case TypeCode.Int64:
                            if (nullable)
                                conv = new NullableXConverter<long>(new LongXConverter());
                            else
                                conv = new LongXConverter();
                            break;

                        case TypeCode.SByte:
                            if (nullable)
                                conv = new NullableXConverter<sbyte>(new SByteXConverter());
                            else
                                conv = new SByteXConverter();
                            break;

                        case TypeCode.Single:
                            if (nullable)
                                conv = new NullableXConverter<float>(new FloatXConverter());
                            else
                                conv = new FloatXConverter();
                            break;

                        case TypeCode.String:
                            conv = new StringXConverter();
                            break;

                        case TypeCode.UInt16:
                            if (nullable)
                                conv = new NullableXConverter<ushort>(new UShortXConverter());
                            else
                                conv = new UShortXConverter();
                            break;

                        case TypeCode.UInt32:
                            if (nullable)
                                conv = new NullableXConverter<uint>(new UIntXConverter());
                            else
                                conv = new UIntXConverter();
                            break;

                        case TypeCode.UInt64:
                            if (nullable)
                                conv = new NullableXConverter<ulong>(new ULongXConverter());
                            else
                                conv = new ULongXConverter();
                            break;

                        case TypeCode.Object:

                            if (t == typeof(Guid))
                            {
                                if (nullable)
                                    conv = new NullableXConverter<Guid>(new GuidXConverter());
                                else
                                    conv = new GuidXConverter();
                            }
                            else if (t == typeof(Color))
                            {
                                if (nullable)
                                    conv = new NullableXConverter<Color>(new HTMLColorXConverter());
                                else
                                    conv = new HTMLColorXConverter();
                            }
                            else if (t == typeof(TimeSpan))
                            {
                                if (nullable)
                                    conv = new NullableXConverter<TimeSpan>(new TimeSpanXConverter());
                                else
                                    conv = new TimeSpanXConverter();
                            }
                            else if (t == typeof(Point))
                            {
                                if (nullable)
                                    conv = new NullableXConverter<Point>(new PointXConverter());
                                else
                                    conv = new PointXConverter();
                            }
                            else if (t == typeof(Uri))
                                conv = new UriXConverter();
                            else if (t == typeof(byte[]))
                                conv = new BinaryXConverter();
                            else if (t == typeof(Image))
                                conv = new ImageXConverter();
                            break;

                        case TypeCode.Empty:
                        case TypeCode.DBNull:
                        default:
                            throw new ArgumentOutOfRangeException("TypeCode." + tc.ToString());

                    }
                }
            }
            catch (Exception ex)
            {
                throw new ArgumentException("Could not create the Converter for type '" + t.Name + "' because :" + ex.Message, ex);
            }

            return (XConverter<T>)conv;
        }

        #endregion
    }
}
